package cn.wizzer.app.crown.modules.services.impl.order;

import cn.wizzer.app.crown.commons.util.DateUtil;
import cn.wizzer.app.crown.modules.models.CrownLeague;
import cn.wizzer.app.crown.modules.models.CrownMatch;
import cn.wizzer.app.crown.modules.models.CrownOrder;
import cn.wizzer.app.crown.modules.models.CrownOrderDetail;
import cn.wizzer.app.crown.modules.models.caches.CrownMatchChampion;
import cn.wizzer.app.crown.modules.models.caches.CrownMatchSimplex;
import cn.wizzer.app.crown.modules.models.constant.CrownConstant;
import cn.wizzer.app.crown.modules.models.constant.CrownRedisConstant;
import cn.wizzer.app.crown.modules.models.enums.MatchTypeEnum;
import cn.wizzer.app.crown.modules.models.enums.PlayWayEnum;
import cn.wizzer.app.crown.modules.models.enums.SettleStatusEnum;
import cn.wizzer.app.crown.modules.models.vo.CrownOrderVO;
import cn.wizzer.app.crown.modules.services.BKSettleService;
import cn.wizzer.app.crown.modules.services.CrownLeagueService;
import cn.wizzer.app.crown.modules.services.CrownMatchService;
import cn.wizzer.app.crown.modules.services.FTSettleService;
import cn.wizzer.app.crown.modules.services.impl.util.PlayWayUtil;
import cn.wizzer.app.crown.modules.services.order.CrownOrderDetailService;
import cn.wizzer.app.crown.modules.services.order.CrownOrderService;
import cn.wizzer.app.match.modules.models.constant.Fields;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.user.modules.models.UserAccountRecord;
import cn.wizzer.app.user.modules.models.UserRestriction;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.services.UserRestrictionService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.Id;
import cn.wizzer.framework.util.JSONUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import org.apache.commons.collections.CollectionUtils;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = CrownOrderService.class)
public class CrownOrderServiceImpl extends BaseServiceImpl<CrownOrder> implements CrownOrderService {
    public CrownOrderServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private CommonDBService commonDBService;
    @Inject
    private CrownMatchService matchService;
    @Inject
    private CrownLeagueService leagueService;
    @Inject
    private CrownOrderDetailService orderDetailService;
    @Inject
    private RedisService redisService;
    @Inject
    private BKSettleService bkSettleService;
    @Inject
    private FTSettleService ftSettleService;

    @Reference(retries = 0)
    @Inject
    private UserAccountRecordService recordService;

    @Reference
    @Inject
    private SysParamService sysParamService;

    @Reference
    @Inject
    private UserRestrictionService restrictionService;

    private static final Log log = Logs.get();

    /**
     * 半场玩法集合
     */
    private ArrayList<Integer> halfBetsList = Result.createList(5, 6, 7, 8, 10, 32, 36, 37, 38);

    @Override
    public Pagination listPage(String orderType, String orderStatus, String dateType,
                               String searchKeyword, String[] searchDate, int pageNumber, int pageSize) {
        log.info("listPage sql");
        // 查询订单的Sql
        Sql orderSql = Sqls.create("SELECT o.*,u.nickName,u.phone " +
                "FROM crown_order o ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM crown_order o ");
        String joinSql = "LEFT JOIN crown_order_detail od ON o.id = od.order_id " +
                "LEFT JOIN user u ON u.id = o.user_id " +
                "$condition ";
        orderSql.appendSourceSql(joinSql);
        countSql.appendSourceSql(joinSql);
        Cnd cnd = Cnd.NEW();
        // 按订单类型搜索
        if (Strings.isNotBlank(orderType)) {
            cnd.and("o.play_way", "=", orderType);
        }
        // 按订单状态条件搜索
        if (Strings.isNotBlank(orderStatus)) {
            cnd.and("o.status", "=", orderStatus);
        }
        // 按日期类型搜索(早盘、今日、滚球)
        if (Strings.isNotBlank(dateType)) {
            cnd.and("od.date_type", "=", dateType);
        }
        // 按昵称/手机号/订单号搜索
        if (Strings.isNotBlank(searchKeyword)) {
            searchKeyword = "%" + searchKeyword + "%";
            cnd.and(Cnd.exps("u.nickName", "like", searchKeyword)
                    .or("u.phone", "like", searchKeyword)
                    .or("o.no", "like", searchKeyword));
        }
        // 按时间搜索
        if (null != searchDate && searchDate.length != 0) {
            cnd.and("o.ctAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("o.ctAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        orderSql.setCondition(cnd);
        orderSql.appendSourceSql("order by o.ctAt desc");
        countSql.setCondition(cnd);

        Pagination pagination = commonDBService.listPage(CrownOrderVO.class, pageNumber, pageSize, orderSql.toString(), countSql.toString());
        log.info("listPage end");
        // 翻译玩法用到的simplex对象
        CrownMatchSimplex simplex = new CrownMatchSimplex();
        // 遍历集合组装数据
        pagination.getList(CrownOrderVO.class).forEach(orderVO -> {
            // 查询订单详情
            List<CrownOrderDetail> orderDetailList = orderDetailService.query(Cnd.where("orderId", "=", orderVO.getId()));
            // 单式
            if (PlayWayEnum.R.equals(orderVO.getPlayWay())) {
                // 单式订单和订单条目是一对一，所以get0
                CrownOrderDetail orderDetail = orderDetailList.get(0);
                // 组装订单关联的赛事信息、翻译玩法
                this.assembleSimplexData(orderVO, orderDetail, simplex);
                // order赋值条目属性
                orderVO.setDetails(orderDetailList);
            } else if (PlayWayEnum.P3.equals(orderVO.getPlayWay())) {
                // 综合
                for (CrownOrderDetail orderDetail : orderDetailList) {
                    // 组装订单关联的赛事信息、翻译玩法
                    this.assembleSimplexData(orderVO, orderDetail, simplex);
                }
                // order赋值条目属性
                orderVO.setDetails(orderDetailList);
            } else {
                // 冠军订单和订单条目是一对一，所以get0
                CrownOrderDetail orderDetail = orderDetailList.get(0);
                this.assembleChampionData(orderDetail);
                // order赋值条目属性
                orderVO.setDetails(orderDetailList);
            }
        });
        return pagination;
    }


//    @Aop(TransAop.REPEATABLE_READ)
//    @Override
//    public void artificial(String orderId, BigDecimal amount, String remark, String platformUid) {
//        CrownOrder order = this.fetch(orderId);
//        //已结算
//        order.setStatus(SettleStatusEnum.SETTLED);
//        order.setRemark(remark);
//        order.setOpAt(System.currentTimeMillis() / 1000);
//        order.setOpBy(platformUid);
//        order.setResultAmount(amount);
//        //修改订单
//        this.update(order);
//        //结算
//        recordService.add(order.getUserId(), amount, BizTypeEnum.BALL_RESULT, order.getId());
//
//    }


    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public void createSimplex(String appid, BigDecimal amount, CrownMatchSimplex simplex) {
        // 检验赔率
        if (null == simplex.getOdds() || simplex.getOdds().compareTo(BigDecimal.ZERO) < 1) {
            throw new BizException("赔率错误，注单失败");
        }

        // 校验下注的玩法是否存在
        String redisKey = CrownRedisConstant.SIMPLEX + simplex.getCrownMatchId() + ":" + simplex.getNo1() + ":" + simplex.getNo2() + ":" + simplex.getNo3();

        CrownMatchSimplex temp = JSONUtil.parseStringToJavaBean(CrownMatchSimplex.class, redisService.get(redisKey));

        if (!simplex.equals(temp)) {
            throw new BizException("停止注单");
        }
        // 校验最高最低及总的注单金额限制
        this.checkOrderRestriction(appid, amount);
        // 创建订单
        CrownOrder order = new CrownOrder();
        order.setAmount(amount);
        order.setNo(Id.createOrderNo());
        order.setMatchType(simplex.getMatchType());
        order.setPlayWay(simplex.getPlayWay());
        order.setUserId(appid);
        // 判断是否滚球注单
        CrownMatch crownMatch = matchService.fetch(simplex.getCrownMatchId());
        if (CrownConstant.RUNNING.equals(crownMatch.getStage())) {
            order.setStatus(SettleStatusEnum.WAIT_CONFIRM);
        } else {
            order.setStatus(SettleStatusEnum.WAIT_SETTLE);
        }
        this.insert(order);

        CrownOrderDetail orderDetail = new CrownOrderDetail();
        orderDetail.setOrderId(order.getId());
        orderDetail.setGameId(crownMatch.getId());
        orderDetail.setNo1(simplex.getNo1());
        orderDetail.setNo2(simplex.getNo2());
        orderDetail.setNo3(simplex.getNo3());
        orderDetail.setCoeNum(simplex.getCoeNum());
        orderDetail.setCoeWord(simplex.getCoeWord());
        orderDetail.setOdds(simplex.getOdds().setScale(2, RoundingMode.DOWN));
        orderDetail.setDateType(crownMatch.getStage());
        orderDetail.setPlayWay(simplex.getPlayWay());
        // 设置orderDetail状态
        if (CrownConstant.RUNNING.equals(crownMatch.getStage())) {
            // 如果是滚球设置成待确认
            orderDetail.setStatus(SettleStatusEnum.WAIT_CONFIRM);
            // 记录比分
            String matchKey = Fields.PREFIX_CROWN + crownMatch.getType() + Fields.KEY_JOIN + PlayWayEnum.R + Fields.KEY_JOIN
                    + CrownConstant.RUNNING.toUpperCase() + Fields.SUFFIX_MATCH + Fields.KEY_COLON + simplex.getCrownMatchId();
            CrownMatch redisMatch = JSONUtil.parseStringToJavaBean(CrownMatch.class, redisService.get(matchKey));
            if (Strings.isNotBlank(redisMatch.getScore())) {
                String[] scoreArray = redisMatch.getScore().split("-");
                orderDetail.setHIn(scoreArray[0]);
                orderDetail.setCIn(scoreArray[1]);
            }
        } else {
            // 否则设置为待结算
            orderDetail.setStatus(SettleStatusEnum.WAIT_SETTLE);
        }
        orderDetailService.insert(orderDetail);

        // 划账
        recordService.add(appid, amount, BizTypeEnum.BALL_BET, order.getId());
    }

    @Aop(TransAop.REPEATABLE_READ)
    @Override
    public void createComplex(String appid, BigDecimal amount, List<CrownMatchSimplex> simplexs) {
        // 如果下注数量小于2，返回错误信息
        if (simplexs.size() < 3 || simplexs.size() > 10) {
            throw new BizException("综合类下注至少三串，最多十串");
        }
        // 校验赛事类型是否一致
        if (simplexs.stream().map(CrownMatchSimplex::getMatchType).distinct().count() > 1) {
            throw new BizException("赛事类型不一致");
        }
        // 校验玩法信息是否正确
        Set<String> matchIds = new HashSet<>(simplexs.size());
        for (int i = 0; i < simplexs.size(); i++) {
            // 校验赔率
            if (null == simplexs.get(i).getOdds() || simplexs.get(i).getOdds().compareTo(BigDecimal.ZERO) < 1) {
                throw new BizException("赔率错误，注单失败");
            }
            // 校验下单赛事是否重复
            if (!matchIds.add(simplexs.get(i).getCrownMatchId())) {
                throw new BizException("综合类每场比赛只能选择一种玩法");
            }
            // 校验下注的玩法是否存在
            String redisKey = CrownRedisConstant.P3 + simplexs.get(i).getCrownMatchId() + ":" + simplexs.get(i).getNo1() + ":" + simplexs.get(i).getNo2() + ":" + simplexs.get(i).getNo3();

            CrownMatchSimplex temp = JSONUtil.parseStringToJavaBean(CrownMatchSimplex.class, redisService.get(redisKey));

            if (!simplexs.get(i).equals(temp)) {
                throw new BizException("停止注单");
            }

            CrownMatch crownMatch = matchService.fetch(simplexs.get(i).getCrownMatchId());
            if (null == crownMatch) {
                throw new BizException("赛事不存在");
            }
            // 判断是否滚球注单
            if (CrownConstant.RUNNING.equals(crownMatch.getStage())) {
                throw new BizException("三串一注单不能包含滚球赛事");
            }
        }
        // 校验最高最低及总的注单金额限制
        this.checkOrderRestriction(appid, amount);
        // 创建订单
        CrownOrder order = new CrownOrder();
        order.setUserId(appid);
        order.setNo(Id.createOrderNo());
        order.setAmount(amount);
        order.setMatchType(simplexs.get(0).getMatchType());
        order.setPlayWay(simplexs.get(0).getPlayWay());
        // 综合类没有滚球数据。设置订单状态为待结算
        order.setStatus(SettleStatusEnum.WAIT_SETTLE);
        // 插入订单
        this.insert(order);

        // 订单详情
        for (int i = 0; i < simplexs.size(); i++) {
            CrownOrderDetail orderDetail = new CrownOrderDetail();
            orderDetail.setOrderId(order.getId());
            orderDetail.setOdds(simplexs.get(i).getOdds().setScale(2, RoundingMode.DOWN));
            orderDetail.setCoeWord(simplexs.get(i).getCoeWord());
            orderDetail.setCoeNum(simplexs.get(i).getCoeNum());
            orderDetail.setNo1(simplexs.get(i).getNo1());
            orderDetail.setNo2(simplexs.get(i).getNo2());
            orderDetail.setNo3(simplexs.get(i).getNo3());
            CrownMatch crownMatch = matchService.fetch(simplexs.get(i).getCrownMatchId());
            orderDetail.setDateType(crownMatch.getStage());
            orderDetail.setGameId(crownMatch.getId());
            orderDetail.setPlayWay(simplexs.get(i).getPlayWay());
            // 设置orderDetail状态为待结算
            orderDetail.setStatus(SettleStatusEnum.WAIT_SETTLE);
            orderDetailService.insert(orderDetail);
        }

        // 划账
        recordService.add(appid, amount, BizTypeEnum.BALL_BET, order.getId());

    }

    @Aop(TransAop.REPEATABLE_READ)
    @Override
    public void createChampion(String appid, BigDecimal amount, CrownMatchChampion champion) {
        // 校验赔率
        if (null == champion.getOdds() || champion.getOdds().compareTo(BigDecimal.ZERO) < 1) {
            throw new BizException("赔率错误，注单失败");
        }
        // 校验玩法
        List<String> lrange = redisService.lrange(CrownRedisConstant.CHAMPION + champion.getLeagueId(), 0, -1);
        boolean flag = false;
        for (int i = 0; i < lrange.size(); i++) {
            CrownMatchChampion temp = JSONUtil.parseStringToJavaBean(CrownMatchChampion.class, lrange.get(i));
            if (champion.equals(temp)) {
                flag = true;
                break;
            }
        }
        if (!flag) {
            throw new BizException("停止注单");
        }
        // 校验最高最低及总的注单金额限制
        this.checkOrderRestriction(appid, amount);
        // 创建order
        CrownLeague crownLeague = leagueService.fetch(champion.getLeagueId());
        CrownOrder order = new CrownOrder();
        order.setNo(Id.createOrderNo());
        order.setAmount(amount);
        order.setMatchType(crownLeague.getType());
        order.setPlayWay(PlayWayEnum.FS);
        order.setUserId(appid);
        order.setStatus(SettleStatusEnum.WAIT_SETTLE);
        this.insert(order);
        // 创建orderDetail
        CrownOrderDetail orderDetail = new CrownOrderDetail();
        orderDetail.setOrderId(order.getId());
        orderDetail.setGameId(crownLeague.getId());
        // 冠军下注的队伍存储在orderDetail表中的coe_word字段。
        orderDetail.setCoeWord(champion.getTeam());
        orderDetail.setOdds(champion.getOdds().setScale(2, RoundingMode.DOWN));
        orderDetail.setDateType(crownLeague.getStage());
        orderDetail.setPlayWay(PlayWayEnum.FS);
        // 设置orderDetail状态为待结算
        orderDetail.setStatus(SettleStatusEnum.WAIT_SETTLE);
        orderDetailService.insert(orderDetail);

        // 划账
        recordService.add(appid, amount, BizTypeEnum.BALL_BET, order.getId());
    }

    @Override
    public Pagination list(String appid, String orderType, String matchType, String playWay, Long startTime, Long endTime, int pageNo, int pageSize) {
        // 查询order的sql
        Sql orderSql = Sqls.create("select id,match_type,play_way,no,amount,resultAmount,status,ctAt " +
                "from crown_order " +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM crown_order $condition ");
        Cnd cnd = Cnd.where("user_id", "=", appid)
                .and("delFlag", "=", 0);
        // 根据订单状态(待确认、待结算、已结算、已取消)筛选
        if (Strings.isNotBlank(orderType)) {
            cnd.and("status", "=", orderType);
        }
        // 根据赛事类型(FOOTBALL BASKETBALL)筛选
        if (Strings.isNotBlank(matchType)) {
            cnd.and("match_type", "=", matchType);
        }
        // 按订单类型搜索
        if (Strings.isNotBlank(playWay)) {
            cnd.and("play_way", "=", playWay);
        }
        // 根据时间筛选
        if (null != startTime && null != endTime) {
            cnd.and("ctAt", ">=", startTime);
            cnd.and("ctAt", "<=", endTime);
        }
        orderSql.setCondition(cnd);
        orderSql.appendSourceSql("order by ctAt desc");
        countSql.setCondition(cnd);

        Pagination pagination = this.listPage(pageNo, pageSize, orderSql, countSql);
        CrownMatchSimplex simplex = new CrownMatchSimplex();
        // 遍历订单列表，组装玩法信息。
        List<CrownOrder> list = pagination.getList(CrownOrder.class);
        for (int i = 0; i < list.size(); i++) {
            CrownOrder order = list.get(i);
            // 查询orderDetail
            List<CrownOrderDetail> orderDetailList = orderDetailService.query(Cnd.where("order_id", "=", order.getId()));
            // 组装冠军数据
            if (PlayWayEnum.FS.equals(order.getPlayWay())) {
                this.assembleChampionData(orderDetailList.get(0));
                order.setDetails(orderDetailList);
            } else if (PlayWayEnum.R.equals(order.getPlayWay())) {
                // 单式玩法
                this.assembleSimplexData(order, orderDetailList.get(0), simplex);
                order.setDetails(orderDetailList);
            } else {
                // 综合过关玩法‘足球 | 综合过关(3场)’。前端大佬说不要‘足球 |’
                // 综合
                for (CrownOrderDetail orderDetail : orderDetailList) {
                    // 组装订单关联的赛事信息、翻译玩法
                    this.assembleSimplexData(order, orderDetail, simplex);
                }
                order.setDetails(orderDetailList);
            }
        }

        return pagination;
    }

    @Override
    public CrownOrder detail(String appid, String orderId) {
        // 查询出订单
        CrownOrder order = this.fetch(Cnd.where("id", "=", orderId)
                .and("user_id", "=", appid)
                .and("delFlag", "=", 0));
        if (null == order) {
            throw new BizException("订单不存在");
        }
        // 查询订单详情
        Sql detailSql = Sqls.create("select date_type,h_in,c_in,coe_word,coe_num,no1,no3,odds,game_id,status");
        detailSql.appendSourceSql("from crown_order_detail where order_id = @orderId order by opAt desc")
                .setParam("orderId", orderId);
        List<CrownOrderDetail> orderDetailList = commonDBService.list(CrownOrderDetail.class, detailSql.toString());
        if (CollectionUtils.isEmpty(orderDetailList)) {
            throw new BizException("订单详情不存在");
        }
        CrownMatchSimplex simplex = new CrownMatchSimplex();
        // 可赢金额
        BigDecimal winAmount;
        // 冠军玩法
        if (PlayWayEnum.FS.equals(order.getPlayWay())) {
            CrownOrderDetail orderDetail = orderDetailList.get(0);
            // 组装数据
            this.assembleChampionData(orderDetail);
            // 计算可赢金额
            winAmount = this.championProfit(order.getAmount(), orderDetail.getOdds());
        } else if (PlayWayEnum.R.equals(order.getPlayWay())) {
            // 单式玩法
            CrownOrderDetail orderDetail = orderDetailList.get(0);
            // 组装数据
            this.assembleSimplexData(order, orderDetail, simplex);
            // 计算可赢金额
            winAmount = this.profitSimplex(order.getAmount(), simplex);
        } else {
            // 综合玩法,遍历集合,组装数据
            for (int i = 0; i < orderDetailList.size(); i++) {
                CrownOrderDetail orderDetail = orderDetailList.get(i);
                this.assembleSimplexData(order, orderDetail, simplex);
            }
            // 计算可赢金额
            winAmount = this.profitP3(order.getAmount(), orderDetailList);
        }
        // set可盈金额
        order.setWinAmount(winAmount);
        // set实际盈亏
        BigDecimal relAmount = null == order.getResultAmount() ? BigDecimal.ZERO : order.getResultAmount().subtract(order.getAmount());
        order.setRelAmount(relAmount);
        // set详情列表
        order.setDetails(orderDetailList);
        return order;
    }

    @Override
    public synchronized void settleAuto() {
        Long start = System.currentTimeMillis();
        log.info("自动结算查询单式、冠军可结算订单查询开始");
        // 查询单式、冠军可结算订单。可结算条件：赛事‘已过中场’,联赛状态为finished、other并且订单状态是WAIT_SETTLE。
        Sql sql = Sqls.create("SELECT o.* FROM crown_order_detail AS od LEFT JOIN crown_order AS o ON o.id = od.order_id")
                .appendSourceSql("LEFT JOIN crown_match AS m ON m.id = od.game_id")
                .appendSourceSql("LEFT JOIN crown_league AS l ON l.id = od.game_id")
                .appendSourceSql("WHERE o.status = @orderStatus")
                .appendSourceSql("AND (o.play_way = 'R' OR o.play_way = 'FS')")
                .appendSourceSql("AND ((m.stage = 'finished' OR m.stage = 'other') OR (l.stage = 'finished' OR l.stage = 'other'))")
                .setParam("orderStatus", SettleStatusEnum.WAIT_SETTLE);

        List<CrownOrder> list = commonDBService.list(CrownOrder.class, sql.toString());
        log.info("查询单式、冠军可结算订单结束");
        Long end = System.currentTimeMillis();
        log.info("=======查询单式、冠军可结算订单 耗时：" + (end - start));

        // 查询P3可结算订单。可结算条件:所有订单条目关联的赛事都已过结束,订单状态是WAIT_SETTLE。
        Sql p3Sql = Sqls.create("SELECT o.*,count(od.id) AS odNum,count(m.id) AS mNum FROM crown_order_detail AS od")
                .appendSourceSql("LEFT JOIN crown_order AS o ON od.order_id = o.id")
                .appendSourceSql("LEFT JOIN crown_match AS m ON m.id = od.game_id")
                .appendSourceSql("AND (m.stage = 'finished' OR m.stage = 'other')")
                .appendSourceSql("WHERE o.play_way = 'P3' AND o.status = @orderStatus")
                .appendSourceSql("GROUP BY o.id HAVING odNum = mNum")
                .setParam("orderStatus", SettleStatusEnum.WAIT_SETTLE);
        List<CrownOrder> p3List = commonDBService.list(CrownOrder.class, p3Sql.toString());
        log.info("=======查询P3可结算订单 耗时：" + (System.currentTimeMillis() - end));

        end = System.currentTimeMillis();
        // 查询半场可结算订单
        Sql halfSql = Sqls.create("SELECT o.* FROM crown_order_detail AS od LEFT JOIN crown_match AS m ON od.game_id = m.id")
                .appendSourceSql("LEFT JOIN crown_order AS o ON od.order_id = o.id")
                .appendSourceSql("WHERE o.status = @orderStatus AND m.is_half_gone = true AND m.stage = 'running'")
                .appendSourceSql("AND o.play_way = 'R'")
                .appendSourceSql("AND ((od.no1 IN (@halfBetsList) and m.type = 'FOOTBALL')")
                .appendSourceSql("or (od.no1 IN (6,7,8) and m.type = 'BASKETBALL'))")// 篮球目前没有半场玩法no1
                .setParam("orderStatus", SettleStatusEnum.WAIT_SETTLE)
                .setParam("halfBetsList", halfBetsList);
        List<CrownOrder> halfList = commonDBService.list(CrownOrder.class, halfSql.toString());
        log.info("=======查询半场可结算订单 耗时：" + (System.currentTimeMillis() - end));

        end = System.currentTimeMillis();
        // 合并集合
        list.addAll(p3List);
        list.addAll(halfList);
        // 遍历集合逐条结算
        for (int i = 0; i < list.size(); i++) {
            CrownOrder order = list.get(i);
            log.info("准备结算的订单：" + order);
            try {
                if (MatchTypeEnum.FOOTBALL.equals(order.getMatchType())) {
                    ftSettleService.settle(order);
                } else {
                    bkSettleService.settle(order);
                }
            } catch (BizException e) {
                log.error(e.getRemand());
            } catch (Exception e) {
                log.error(e);
            }
            log.info("结算结束的订单：" + order);
        }
        log.info("=======结算订单条数：" + list.size() + "结算计算耗时：" + (System.currentTimeMillis() - end));
    }

    @Override
    public void settleManual(String orderId, String platformUid) throws BizException {
        // 结算单笔订单
        CrownOrder order = this.fetch(orderId);
        // 如果是‘待结算’状态，进行结算
        if (SettleStatusEnum.WAIT_SETTLE.equals(order.getStatus())) {
            // 设置操作人。‘操作时间’在结算程序内部有set
            order.setOpBy(platformUid);
            if (MatchTypeEnum.FOOTBALL.equals(order.getMatchType())) {
                ftSettleService.settle(order);
            } else {
                bkSettleService.settle(order);
            }
        } else {
            throw new BizException("订单不是‘待结算状态’");
        }

    }


    @Override
    public synchronized void orderConfirm() {
        //注单确认时间-秒
        long confirmSecond = sysParamService.fetch().getConfirmRunningOrderSecond();
        // 获取待确认订单集合
        List<CrownOrder> list = this.query(Cnd.where("status", "=", SettleStatusEnum.WAIT_CONFIRM));
        for (CrownOrder order : list) {
            // 足球滚球按比分判断
            if (MatchTypeEnum.FOOTBALL.equals(order.getMatchType())) {
                this.orderConfirmWithScore(order, confirmSecond);
            }
            // 篮球滚球按赔率判断
            else if (MatchTypeEnum.BASKETBALL.equals(order.getMatchType())) {
                this.orderConfirmWithOdds(order, confirmSecond);
            } else {
                throw new BizException("定时确认滚球，赛事类型不存在:" + order.getMatchType());
            }
        }

    }


    @Override
    public void reSettle(CrownMatch match) {
        if (MatchTypeEnum.FOOTBALL.equals(match.getType())) {
            ftSettleService.reSettle(match);
        } else if (MatchTypeEnum.BASKETBALL.equals(match.getType())) {
            bkSettleService.reSettle(match);
        }
    }

    @Override
    public void reSettle(CrownLeague league) {
        if (MatchTypeEnum.FOOTBALL.equals(league.getType())) {
            ftSettleService.reSettle(league);
        } else if (MatchTypeEnum.BASKETBALL.equals(league.getType())) {
            bkSettleService.reSettle(league);
        }
    }


    @Aop(TransAop.REPEATABLE_READ)
    private void orderConfirmWithOdds(CrownOrder order, long confirmSecond) {
        CrownOrderDetail orderDetail = orderDetailService.fetch(Cnd.where("order_id", "=", order.getId()));
        // 如果当前时间 - 订单创建时间超过30s，当注单赔率、参数和当前一致时，将订单状态改为‘待结算’
        if (System.currentTimeMillis() / 1000 - order.getCtAt() > confirmSecond) {
            // 拼接redisKey
            String redisKey = CrownRedisConstant.SIMPLEX + orderDetail.getGameId() + ":"
                    + orderDetail.getNo1() + ":" + orderDetail.getNo2() + ":" + orderDetail.getNo3();
            // 获取redis中现在的赔率和参数
            CrownMatchSimplex redisSimplex = JSONUtil.parseStringToJavaBean(CrownMatchSimplex.class,
                    redisService.get(redisKey));
            // 如果注单赔率、参数和当前一致，将订单状态改为‘待结算’
            if (null != redisSimplex.getOdds() && redisSimplex.getOdds().compareTo(orderDetail.getOdds()) == 0) {
                order.setStatus(SettleStatusEnum.WAIT_SETTLE);
                orderDetail.setStatus(SettleStatusEnum.WAIT_SETTLE);
                order.setOpAt(System.currentTimeMillis() / 1000);
                orderDetail.setOpAt(System.currentTimeMillis() / 1000);
                this.update(order);
                orderDetailService.update(orderDetail);
            } else {
                // 撤销注单
                this.revert(order, "task");
            }
        }
    }


    @Aop(TransAop.REPEATABLE_READ)
    private void orderConfirmWithScore(CrownOrder order, long confirmSecond) {
        // 获取待确认订单集合
        CrownOrderDetail orderDetail = orderDetailService.fetch(Cnd.where("order_id", "=", order.getId()));
        // 如果当前时间 - 订单创建时间超过30s，当注单主客队比分和当前redis中的一致时，将订单状态改为‘待结算’
        if (System.currentTimeMillis() / 1000 - order.getCtAt() > confirmSecond) {
            // 拼接redis的key
            String matchKey = Fields.PREFIX_CROWN + order.getMatchType() + Fields.KEY_JOIN + PlayWayEnum.R + Fields.KEY_JOIN
                    + CrownConstant.RUNNING.toUpperCase() + Fields.SUFFIX_MATCH + Fields.KEY_COLON + orderDetail.getGameId();
            CrownMatch redisMatch = JSONUtil.parseStringToJavaBean(CrownMatch.class, redisService.get(matchKey));
            // 判断比分。如果赛事存在，并且此时redis中的主客队比分和数据库中注单记录的比分一致，则转为‘待结算’状态
            if (null != redisMatch
                    && Strings.isNotBlank(redisMatch.getScore())
                    && redisMatch.getScore().split("-")[0].equals(orderDetail.getHIn())
                    && redisMatch.getScore().split("-")[1].equals(orderDetail.getCIn())
            ) {
                order.setStatus(SettleStatusEnum.WAIT_SETTLE);
                orderDetail.setStatus(SettleStatusEnum.WAIT_SETTLE);
                order.setOpAt(System.currentTimeMillis() / 1000);
                orderDetail.setOpAt(System.currentTimeMillis() / 1000);
                this.update(order);
                orderDetailService.update(orderDetail);
            } else {
                // 撤销注单
                this.revert(order, "task");
            }
        }
    }

    /**
     * 撤销订单
     *
     * @param order
     */
    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public void revert(CrownOrder order, String platformUid) {
        if (!order.getStatus().equals(SettleStatusEnum.WAIT_SETTLE)) {
            Result.breakWith("订单不是‘待结算’状态");
        }
        order.setSettleAt(System.currentTimeMillis() / 1000);
        order.setOpBy(platformUid);
        //异常注单
        order.setStatus(SettleStatusEnum.CANCELLED);
        //修改订单
        this.update(order);
        orderDetailService.update(Chain.make("status", SettleStatusEnum.CANCELLED),
                Cnd.where("order_id", "=", order.getId()));
        //单额回退
        recordService.add(order.getUserId(), order.getAmount(), BizTypeEnum.BALL_REVERT, order.getId());
    }


    /**
     * 单式、综合玩法数据组装
     *
     * @param order
     * @param orderDetail
     * @param simplex
     */
    private void assembleSimplexData(CrownOrder order, CrownOrderDetail orderDetail, CrownMatchSimplex simplex) {
        CrownMatch match = matchService.fetch(Cnd.where("id", "=", orderDetail.getGameId()));
        if (null == match) {
            throw new BizException("注单对应的赛事不存在");
        }
        // 拼接标题‘足球 | 球会友谊赛’。
        orderDetail.setTitle(match.getLeague());
        // 拼接子标题‘里德 VS 杜克拉布拉格’
        orderDetail.setTitleSub(match.getMbTeam() + " VS " + match.getTgTeam());
        // 翻译玩法
        simplex.setMatchType(order.getMatchType());
        simplex.setCoeNum(orderDetail.getCoeNum());
        simplex.setCoeWord(orderDetail.getCoeWord());
        simplex.setNo1(orderDetail.getNo1());
        simplex.setNo3(orderDetail.getNo3());
        simplex.setMbTeam(match.getMbTeam());
        simplex.setTgTeam(match.getTgTeam());
        simplex.setOdds(orderDetail.getOdds());
        // 下注内容
        orderDetail.setBetContent(PlayWayUtil.translate(simplex) + " @" + orderDetail.getOdds());
        // 开赛时间
        Date matchStartDate = DateUtil.timeZoneConvert(match.getDateTime(), "America/New_York", "Asia/Shanghai");
        orderDetail.setMatchStartDate(matchStartDate);
        // 比赛状态
        orderDetail.setMatchStage(match.getStage());
        if (Strings.isNotBlank(match.getMbInBall()) && Strings.isNotBlank(match.getTgInBall())) {
            // 主客队全场比分
            orderDetail.setMatchScore(match.getMbInBall() + ":" + match.getTgInBall());
        }
        if (Strings.isNotBlank(match.getMbInBallHf()) && Strings.isNotBlank(match.getTgInBallHf())) {
            // 主客队半场比分
            orderDetail.setMatchHalfScore(match.getMbInBallHf() + ":" + match.getTgInBallHf());
        }
    }

    /**
     * 冠军玩法数据组装
     *
     * @param orderDetail
     */
    private void assembleChampionData(CrownOrderDetail orderDetail) {
        CrownLeague league = leagueService.fetch(Cnd.where("id", "=", orderDetail.getGameId()));
        if (null == league) {
            throw new BizException("注单对应的联赛不存在");
        }
        // 组装订单关联的赛事信息、翻译玩法
        // 拼接标题‘丹麦超级联赛’
        orderDetail.setTitle(league.getLeague());
        // 拼接子标题‘2019/2020-冠军’
        orderDetail.setTitleSub(league.getChampionName());
        // 拼接下注内容‘阿雷玛 @77’
        orderDetail.setBetContent(orderDetail.getCoeWord() + " @" + orderDetail.getOdds().setScale(3, RoundingMode.FLOOR));
        // 开赛时间
        Date matchStartDate = DateUtil.timeZoneConvert(league.getDateTime(), "America/New_York", "Asia/Shanghai");
        orderDetail.setMatchStartDate(matchStartDate);
        // 联赛状态
        orderDetail.setMatchStage(league.getStage());
        // 获得冠军的队伍
        //orderDetail.setMatchScore(league.getWinner());
    }

    /**
     * 单式可赢金额计算
     * 足球的'全场独赢'、'全场单双'、'半场独赢'、'半场波胆'、'全场波胆'、'全场总进球数'、'半场总进球数'、玩法赔率减一
     * 篮球的‘独赢’玩法赔率减一
     *
     * @param amount  下注金额
     * @param simplex 包含‘比赛类型(足球、篮球),玩法(no1,no2,no3...),赔率等信息’
     * @return
     */
    private BigDecimal profitSimplex(BigDecimal amount, CrownMatchSimplex simplex) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            Result.breakWith("金额有误");
        }
        BigDecimal odds = simplex.getOdds();
        boolean sub = false;
        //
        int no1 = simplex.getNo1();
        if (MatchTypeEnum.FOOTBALL.equals(simplex.getMatchType())) {
            // 玩法赔率减一
            if (no1 == 1 || no1 == 4 || no1 == 5 || no1 == 8 || no1 == 9 || no1 == 10 || no1 == 11 || no1 == 12) {
                sub = true;
            }

        } else if (MatchTypeEnum.BASKETBALL.equals(simplex.getMatchType())) {
            // 篮球‘独赢’赔率减一
            if (no1 == 1) {
                sub = true;
            }
        }

        if (sub) {
            odds = odds.subtract(BigDecimal.ONE);
        }
        //计算赢额
        amount = amount.multiply(odds);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            Result.breakWith("费率有误");
        }
        return amount;

    }

    /**
     * 冠军可赢金额计算
     *
     * @param amount 下注金额
     * @param odds   赔率
     * @return
     */
    private BigDecimal championProfit(BigDecimal amount, BigDecimal odds) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            Result.breakWith("金额有误");
        }
        // 足球,篮球冠军玩法计算可赢金额赔率要减一
        odds = odds.subtract(BigDecimal.ONE);
        //计算赢额
        amount = amount.multiply(odds);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            Result.breakWith("费率有误");
        }
        return amount;
    }

    /**
     * 综合可赢金额计算
     *
     * @param amount          下注金额
     * @param orderDetailList 订单详情集合
     * @return
     */
    private BigDecimal profitP3(BigDecimal amount, List<CrownOrderDetail> orderDetailList) {
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            Result.breakWith("金额有误");
        }
        // 综合玩法赔率不用减一,直接累乘
        for (CrownOrderDetail orderDetail : orderDetailList) {
            amount = amount.multiply(orderDetail.getOdds());
        }
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            Result.breakWith("费率有误");
        }
        return amount;
    }


    private void checkOrderRestriction(String userId, BigDecimal amount) {
        //region 次消费限制
        //缓存中取出用户限制数据
        UserRestriction userRestriction = restrictionService.initCache(userId);
        //下注时判断金额限制
        if (amount.compareTo(userRestriction.getTimeMinRestriction()) < 0) {
            Result.breakWith("单次消费低于" + userRestriction.getTimeMinRestriction());
        }
        if (amount.compareTo(userRestriction.getTimeMaxRestriction()) > 0) {
            Result.breakWith("单次消费超出" + userRestriction.getTimeMaxRestriction());
        }

        //region 日消费限制
        long today = Times.ams(Times.nextDay(0)) / 1000;
        //日下注金额
        BigDecimal dayBet = recordService.query(Cnd.where("opAt", ">=", today).and("user_id", "=", userId).and("biz_type", "=", BizTypeEnum.BALL_BET))
                .stream().map(UserAccountRecord::getOperateAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::subtract);
        //日撤销金额
        BigDecimal dayRevert = recordService.query(Cnd.where("opAt", ">=", today).and("user_id", "=", userId).and("biz_type", "=", BizTypeEnum.BALL_REVERT))
                .stream().map(UserAccountRecord::getOperateAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //日消费总额 = 日下注金额 - 日撤销金额 + 将要消费的金额
        BigDecimal dayConsume = dayBet.subtract(dayRevert).add(amount);
        if (dayConsume.compareTo(userRestriction.getDayRestriction()) > 0) {
            Result.breakWith("每日消费超出限制");
        }
        //endregion
        //region 总消费限制
        //总下注金额
        BigDecimal totalBet = recordService.query(Cnd.where("user_id", "=", userId).and("biz_type", "=", BizTypeEnum.BALL_BET))
                .stream().map(UserAccountRecord::getOperateAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::subtract);
        //总撤销金额
        BigDecimal totalRevert = recordService.query(Cnd.where("user_id", "=", userId).and("biz_type", "=", BizTypeEnum.BALL_REVERT))
                .stream().map(UserAccountRecord::getOperateAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        //消费总额 = 总下注金额 - 总撤销金额 + 将要消费的金额
        BigDecimal totalConsume = totalBet.subtract(totalRevert).add(amount);
        if (totalConsume.compareTo(userRestriction.getTotalRestriction()) > 0) {
            Result.breakWith("总消费超出限制");
        }
    }
}
